package com.java.base.exercise.threadpool;

import lombok.SneakyThrows;

import java.util.concurrent.*;

/**
 * @Author RenPu
 * @Date 2023/3/7 10:02
 * @Version 1.0
 * @Description: 不同种类线程池案例的编写
 **/
public class ThreadPoolCategoryTest {


    /**
     * 线程池参数信息： 核心线程数：5，最大线程数：10，队列大小长度：3，
     * 超出核心线程数量的线程存活时间阀值:5秒，指定使用拒绝策略
     */
    public static void threadPoolExceptionTest1(){

        ThreadPoolExecutor threadPoolExecutor =new ThreadPoolExecutor(5, 10, 5, TimeUnit.SECONDS, new LinkedBlockingDeque<>(3), new RejectedExecutionHandler() {
            @Override
            public void rejectedExecution(Runnable runnable, ThreadPoolExecutor threadPoolExecutor) {
                System.err.println("有任务被拒绝执了。。。。。");
            }
        });
        testCommon(threadPoolExecutor);
    }



    /**
     * 队列类型为：SynchronousQueue（同步队列），
     * 适用场景
     * 1：任务数量是动态发生变化的，不可进行评估的，推荐使用此队列
     * 2：快速执行任务也是推荐此队列，最大线程数要进行合理设置
     * 但是需要注意最大线程数的数量设置： 不要设置为Integer.MAX_VALUE，避免资源的紧张导致卡顿
     * 模拟案例参数：线程池参数信息： 核心线程数：0，最大线程数：Integer.MAX_VALUE
     * 超出核心线程数量的线程存活时间阀值:60秒
     *SynchronousQueue会根据提交的任务数量，去获取对应的空闲线程进行来执行，如果没有空闲的线程则根据任务的数量进行加开新线程
     * 同时也会给新加开的线程，设置生命周期
     *
     * SynchronousQueue实际不是一个真正的队列，它不会为队列中的元素进行维护存储空间，它维护的是一组线程，这些线程用于把任务进行添加队列或移除队列
     * 当代码客户端向线程提交任务时，如果SynchronousQueue中的维护的线程组没有空闲线程从队列实例获取一个任务，那么相应的offer（存放任务的方法）就会失败
     * 因此添加队列的任务失败，此时线程池新加线程为失败任务进行处理（前提没有触发最大线程数的限制）
     */
    public static void threadPoolExceptionTest2(){
        ThreadPoolExecutor threadPoolExecutor =new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
                new SynchronousQueue<>());
        testCommon(threadPoolExecutor);
    }


    /**
     * 定时线程池：具有可以周期性往复的执行
     *  如果某一刻执行的定时任务较多，可以多线程数进行适量的调大
     */
    public static void threadPoolSchelendTest(){
        ScheduledThreadPoolExecutor threadPoolExecutor = new ScheduledThreadPoolExecutor(5);

        /**
         * 此方法具体根据任务执行的时间周期为定期执行的时间，
         * 例如：设置定时任务执行周期参数为1秒,任务实际执行了3秒进行处理完成，此时定期执行的时间周期为任务实际执行时间
         * 总结：定时执行的周期，具体以任务执行的时间周期为准，不一定为参数指定的时间周期进行执行
         * 项目业务任务定时，推荐使用scheduleAtFixedRate
         * **/
        threadPoolExecutor.scheduleAtFixedRate(new Runnable() {
            @Override
            @SneakyThrows
            public void run() {
                TimeUnit.SECONDS.sleep(3L);
                System.out.println("消息任务被执行："+System.currentTimeMillis());
            }
        }, 2000L, 1000L, TimeUnit.MILLISECONDS);


        /**
         * 与scheduleAtFixedRate方法区别的是，会根据任务具体执行的时间+1秒，为下次定时任务的周期
         * 具体根据业务需要进行选择
         */
        threadPoolExecutor.scheduleWithFixedDelay(new Runnable() {
            @Override
            @SneakyThrows
            public void run() {
                TimeUnit.SECONDS.sleep(3L);
                System.out.println("消息任务被执行："+System.currentTimeMillis());
            }
        }, 2000L, 1000L, TimeUnit.MILLISECONDS);

    }






    /**
     *
     * 执行线程任务提交的方法，一次性提交十五个任务到线程池
     * @param threadPoolExecutor
     */
    @SneakyThrows
    static void testCommon(ThreadPoolExecutor threadPoolExecutor){
        //向线程池里提交15个任务
        for (int i = 0; i <15 ; i++) {
            int n=i;
            threadPoolExecutor.submit(new Runnable() {
                @SneakyThrows
                @Override
                public void run() {
                    System.out.println("开始执行：当前线程为"+n);
                        //模拟执行任务的耗时时间
                        Thread.sleep(3000L);
                    System.out.println("任务执行结束：当前线程为"+n);
                }
            });
            System.out.println("任务提交完成："+i);
        }


        //查看线程数量，查看队列数量
        Thread.sleep(3000L);
        System.out.println("当前线程池核心线程数量为："+threadPoolExecutor.getPoolSize());
        System.out.println("当前线程池中等待的数量为："+threadPoolExecutor.getQueue().size());

        //等待15秒再次查看核心线程数，以及线程池中等待任务数量
        Thread.sleep(15000L);
        System.out.println("当前线程池核心线程数量为："+threadPoolExecutor.getPoolSize());
        System.out.println("当前线程池中等待的数量为："+threadPoolExecutor.getQueue().size());
    }

    public static void main(String[] args) {
        //threadPoolExceptionTest1();

        threadPoolSchelendTest();
    }



}
